home *** CD-ROM | disk | FTP | other *** search
/ Language/OS - Multiplatform Resource Library / LANGUAGE OS.iso / pcl / sptmbr16.lha / lap.text < prev    next >
Text File  |  1990-01-22  |  26KB  |  656 lines

  1. -*- Mode: Text -*-
  2.  
  3. Copyright (c) 1985, 1986, 1987, 1988, 1989 Xerox Corporation.
  4. All rights reserved.
  5.  
  6. Use and copying of this document is permitted.  Any distribution of this
  7. document must comply with all applicable United States export control
  8. laws.
  9.  
  10. Last updated: 6/3/89 by Gregor
  11.               10/26/89 by Gregor -- added :RETURN, removed :ISHIFT
  12.  
  13. This file contains documentation of the PCL abstract LAP code.  Any port
  14. of PCL is required to implement the abstract LAP code interface.  There
  15. is a portable, relatively good performance implementation in the file
  16. lap.lisp, port-specific implementations are in that file as well.
  17.  
  18. The PCL abstract LAP code mechanism exists to provide PCL with a way to
  19. create high-performance method lookup functions.  Using this mechanism,
  20. PCL can produce "LAP closures" which do the method lookup.  By allowing
  21. PCL to specify these closures using abstract LAP code rather that Lisp
  22. code we hope to achieve the following:
  23.  
  24.   * Better runtime performance.  By using abstract LAP code, we
  25.     will get better machine instruction sequences than we would
  26.     from compiling Lisp code. 
  27.  
  28.   * Better load and update time performance.  Because it should
  29.     be possible to "assemble" the LAP code more quickly than
  30.     compiling Lisp code, PCL will spend less time building the
  31.     method lookup code.
  32.  
  33.   * Ability to use PCL without a compiler.  The LAP assembler will
  34.     still be required but this should be much smaller than the full
  35.     lisp compiler.
  36.  
  37. Of course, not all implementations of the LAP code mechanism will
  38. satisfy all of these goals.  The first is the most important.
  39.  
  40. In particular, many PCL ports will use the portable LAP implementation.
  41. KCL will use the portable implementation in all of its ports.  Other
  42. Lisps may have custom LAP implementations for some ports and use the
  43. portable implementation for other ports.
  44.  
  45. Some Lisps will have a custom LAP implementation but will nonetheless
  46. require the compiler to be loaded to generate LAP closure constructors.
  47.  
  48. An important point is why we have chosen to take this route rather than
  49. have each implementation implement the method lookup codes itself.  This
  50. was done because we are, at PARC, just beginning to study cache behavior
  51. for CLOS programs.  As we learn more about this we will want to modify
  52. the caching strategy PCL uses.  This architecture, because it leaves
  53. PCL to implement caching behavior makes it possible to do this.  Once
  54. this study is complete, implementations may want to do their own, ultra
  55. high performance implementations of caching strategies.
  56.  
  57.  
  58.  
  59. Production of LAP closures is a two step process.  In the first step, a
  60. port-specific function is called to take abstract LAP code and produce a
  61. a "lap closure generator".  Lap closure generators are functions which
  62. are called with a set of closure variable values and return a LAP
  63. closure.
  64.  
  65. The intermediary of the lap closure generators provides an important
  66. optimization.  Because it is assumed that producing the LAP closure
  67. generator can take much longer than producing a LAP closure from the
  68. generator, PCL attempts to make only one closure generator for each
  69. sequence of LAP code.  Because of the way PCL generates the LAP code
  70. sequences, this is quite easy for it to do.
  71.  
  72. The rest of this document is divided into six parts.  
  73.  
  74.   * the metatypes std-instance and fsc-instance
  75.  
  76.   * an abstraction for simple vector indices
  77.  
  78.   * important optimizations
  79.  
  80.   * the port specific function for making lap closure generators
  81.  
  82.   * the actual abstract LAP code
  83.  
  84.   * examples
  85.  
  86. *** The metatypes STD-INSTANCE and FSC-INSTANCE ***
  87.  
  88. In PCL, instances with metaclass STANDARD-CLASS are represented using
  89. the metatype STD-INSTANCE.  (Note that in Cinco de Mayo PCL, this
  90. metatype is called IWMC-CLASS.)  Each port must implement this metatype.
  91. The metatype could be implemented by the following DEFSTRUCT.
  92.  
  93.    (defstruct (std-instance 
  94.                 (:predicate std-instance-p)
  95.                 (:conc-name %std-instance-)
  96.                 (:constructor %allocate-std-instance (wrapper slots))
  97.                 (:constructor %allocate-std-instance-1 ())
  98.                 (:print-function print-std-instance))
  99.      (wrapper nil)
  100.      (slots nil))
  101.  
  102.  PCL itself will guarantee correct access to this structure and the
  103.  accessors and constructors.  With this in mind, the following are
  104.  important.
  105.  
  106.  * Being able to type test this structure quickly is critical. See 
  107.    the :STD-INSTANCE-P opcode.
  108.  
  109.  * The allocation functions should compile inline, do no argument
  110.    checking and be as fast as possible.
  111.  
  112.  * The accessor functions should compile inline, do no checking of their
  113.    arguments and be as fast as possible.  SETF of the accessors should
  114.    do the same.
  115.  
  116. The port is also required to implement the metatype FSC-INSTANCE (called
  117. FUNCALLABLE-INSTANCE, or FIN for short, in Cinco de Mayo PCL).  Objects
  118. with this metatype are used, among other things, to implement generic
  119. functions.  These objects have field structure associated with them and
  120. are also functions that can be applied to arguments.  The fields are the
  121. same as those for STD-INSTANCE, the FSC-INSTANCE metatype has
  122. predicates, print-functions, constructors and accessors as follows:
  123.  
  124.   fsc-instance-p
  125.   print-fsc-instance
  126.   %fsc-instance-wrapper
  127.   %fsc-instance-slots
  128.   %allocate-fsc-instance (wrapper slots)
  129.   %allocate-fsc-instance-1 ()
  130.  
  131. In addition, objects of metatype FSC-INSTANCE have a property called the
  132. funcallable instance function.  When an FSC-INSTANCE is applied to
  133. arguments, the funcallable instance function is what is actually called.
  134. The funcallable instance function of an FSC-INSTANCE can be changed
  135. using the function SET-FUNCALLABLE-INSTANCE-FUNCTION.  There is no
  136. mechanism for obtaining the funcallable instance function of an
  137. FSC-INSTANCE.
  138.  
  139. It is possible to implement the FSC-INSTANCE metatype in pure Common
  140. Lisp. A simple implementation which uses lexical closures as the
  141. instances and a hash table to record that the lexical closures are of
  142. metatype FSC-INSTANCE is easy to write.  Unfortunately, this
  143. implementation adds significant overhead:
  144.  
  145.    to generic-function-invocation (1 function call)
  146.    to slot-access (1 function call or one hash table lookup)
  147.    to class-of a generic-function (1 hash-table lookup)
  148.  
  149. In addition, it would prevent the FSC-INSTANCEs from being garbage
  150. collected.  In short, the pure Common Lisp implementation really isn't
  151. practical.
  152.  
  153. Note that previous implementations of FINS were always based on the
  154. lexical closure metatype.  In some ports, that provides poor
  155. performance.  Those ports may want to consider reimplementing to use the
  156. compiled code metatype.  In that implementation strategy, LAP closure
  157. variables would become constants of the compiled code object.
  158.  
  159. The following note from JonL is of interest when working on a FIN
  160. implementation:
  161.  
  162.   Date: Tue, 16 May 89 05:45:56 PDT
  163.   From: Jon L White <jonl@lucid.com>
  164.   
  165.   This isn't a bug in Lucid's compiler -- it's a lurking bug in PCL
  166.   that will "bite" most implementations where different settings of
  167.   the compiler optimization switches will produce morphologically
  168.   different (but of course functionally equivalent) function objects.
  169.   
  170.   The difficulty is in how discriminator codes service cache misses.  
  171.   They  "call out" to (potentially) random functions that will in some 
  172.   cases "smash" the function object that was actually running as the 
  173.   discriminator code.  This is all right providing you don't return to 
  174.   that function frame, but alas ...
  175.    
  176.   I know this is a more extensive problem because the code in the
  177.   port-independent function 'notice-methods-change' goes out of
  178.   its way to do a tail-recursive call to the function that is going
  179.   to smash the possibly-executing discriminator code.  Here is the
  180.   commentary from that code (sic):
  181.   
  182.   ;; In order to prevent this we take a simple measure:  we just
  183.   ;; make sure that it doesn't try to reference our its own closure
  184.   ;; variables after it makes the dcode change.  This is done by
  185.   ;; having notice-methods-change-2 do the work of making the change
  186.   ;; AND calling the actual generic function (a closure variable)
  187.   ;; over.  This means that at the time the dcode change is made,
  188.   ;; there is a pointer to the generic function on the stack where
  189.   ;; it won't be affected by the change to the closure variables.
  190.   
  191.   
  192.   A similar thing should be done in the construction of standard-accessor, 
  193.   checking, and  caching dcodes.  In an experimental version here at Lucid, 
  194.   I rewrote  dcode.lisp to do that, and there is no problem with it.  
  195.   Although that code is somewhat Lucid-specific, it could be of help to 
  196.   someone who wanted to rewrite the generic dcode.lisp (no pun intended). 
  197.   Contact me privately if you are interested.
  198.   
  199.   Doing a tail-recursive call out of dcodes when there is a cache miss
  200.   is a good thing, regardless of other problems.  I think one might as
  201.   well do it.  However, I should point out that in the presence of 
  202.   multiprocessing, there is another more serious problem that cannot be
  203.   solved so simply.  Think about what happens when one process decides
  204.   to update a dcode while another process is still using it; no such
  205.   stack-maintenance discipline will fix this case.  A tail-recursive
  206.   exit from the dcode will *immensely* reduce the likelihood that
  207.   another process can sneak in during the interval in which the dcode
  208.   requires consistency in its function; but it can't reduce that
  209.   likelihood to zero.
  210.   
  211.   The more desirable thing to do is to put the whole "dcode" down one 
  212.   more level of indirection through the symbol-function cell of the 
  213.   generic function.  This is effectively what PCL's 'make-trampoline' 
  214.   function does, but unfortunately that is not a very efficient approach 
  215.   when you consider how most compilers will compile it.  Something akin 
  216.   to the "mattress-pads" in Steve Haflich's code (in the fin.lisp file) 
  217.   could probably be done for many other implementations as well.
  218.  
  219.  
  220. *** Index Operations ***
  221.  
  222. Indexes are an abstraction for indexes into a simple vector.  This
  223. abstraction is used to make it possible to generate more efficient
  224. code to access simple vectors.  The idea being that this may make it
  225. possible to use alternate addressing modes to address these.
  226.  
  227. The "index value" of an index is defined to be the fixnum of which that
  228. index is an alternate form.  So, using the Lisp function SVREF with the
  229. index value of an index accesses the same element as using the index
  230. with the appropriate access function or operand.
  231.  
  232. The format of an index is unspecified, but is assumed to be something
  233. like a fixnum with certain bits ignored.  Accessing a vector using an
  234. index must be done using the appropriate special accessor function or
  235. opcode.
  236.  
  237. Conversion from index values to indices and vice-versa can be done with
  238. the following functions:
  239.  
  240. INDEX-VALUE->INDEX (index-value)
  241. INDEX->INDEX-VALUE (index)
  242.  
  243.  
  244. The following constant indicates the maximum index value an index can
  245. have in a given port.  This must be at least 2^16.
  246.  
  247. INDEX-VALUE-LIMIT  - a fixnum, must be at least 2^16.
  248.  
  249.  
  250. MAKE-INDEX-MASK (<cache-size> <line-size>)
  251.  
  252. This function is used to make index masks.  Because I am lazy, I show an
  253. implementation of it in the common case where indexes are just fixnums:
  254.  
  255.   (defun make-index-mask (cache-size line-size)
  256.     (let ((cache-size-in-bits (floor (log cache-size 2)))
  257.           (line-size-in-bits (floor (log line-size 2)))
  258.           (mask 0))
  259.       (dotimes (i cache-size-in-bits) (setq mask (dpb 1 (byte 1 i) mask)))
  260.       (dotimes (i line-size-in-bits)  (setq mask (dpb 0 (byte 1 i) mask))) 
  261.       mask))
  262.  
  263. *** Optimizations ***
  264.  
  265. This section discusses two important optimizations related to LAP
  266. closures.  The first relates to calling LAP closures themselves, the
  267. second relates to calling other functions from LAP closures.
  268.  
  269. The important point about calling LAP closures is that almost all of the
  270. time, LAP closures will be used as the funcallable-instance-function of
  271. funcallable instances.  It is required that LAP closures be funcallable
  272. themselves, but usually they will be stored in a FIN and the fin will
  273. then be funcalled.  This brings up several optimizations, including ones
  274. having to do with access to the closure variables of a LAP closure.
  275.  
  276. When a LAP closure is used to do method lookup, the function the LAP
  277. closure ends up calling has the same number of required arguments as the
  278. LAP closure itself.  Since the LAP closure must check its required
  279. arguments to do the lookup, it is redundant for the function called to
  280. do so as well.  Since LAP closures do all calls in a tail recursive way,
  281. it should even be possible to optimize out certain parts of the normal
  282. stack frame initialization.
  283.  
  284. A similar situation occurs between effective method functions and the
  285. individual method functions; the difference is that in effective method
  286. functions, the calls are not necessarily tail recursive.
  287.  
  288. Consequently, it would be nice to have a way to call certain functions
  289. and inhibit the checking of required arguments.  This is made possible
  290. by use of the PCL-FAST-APPLY and PCL-FAST-FUNCALL macros together with
  291. the PCL-FAST-CALL compiler declaration.
  292.  
  293. The PCL-FAST-CALL compiler declaration declares that a function may be
  294. fast called.  Not all callers of the function will necessarily fast call
  295. it, but most probably will.  The :JMP opcode can only be used to call a
  296. function compiled with the PCL-FAST-CALL declaration.
  297.  
  298. The PCL-FAST-APPLY and PCL-FAST-FUNCALL macros are used to fast call a
  299. function.  The function argument must be a compiled function that has
  300. the PCL-FAST-CALL compiler declaration in its lambda declarations.
  301.  
  302. The basic idea is that the PCL-FAST-CALL compiler declaration causes the
  303. compiler to set up an additional entrypoint to the function.  This
  304. entrypoint comes after checking of required arguments but before
  305. processing of other arguments.
  306.  
  307. Note:  When FAST-APPLY is used, the required arguments will be given as
  308. separate arguments and all other arguments will appear as a single
  309. spread argument.  For example:
  310.  
  311. (let ((fn (compile () '(lambda (a b &optional (c 'z))
  312.                          (declare (pcl-fast-call))
  313.                          (list a b c)))))
  314.  
  315.   (pcl-fast-apply fn 'x 'y ())          ;legal
  316.   (pcl-fast-apply fn 'x 'y '(foo))      ;legal
  317.   (pcl-fast-apply fn '(a b c))          ;illegal
  318.   )
  319.  
  320. *** Producing LAP Closure Generators ***
  321.  
  322. Each implementation of the LAP code mechanism must provide a port
  323. specific function making lap closure generators.  In the portable
  324. implementation, this function is called PLAP-CLOSURE-GENERATOR.  In
  325. ExCL it should be called EXCL-LAP-CLOSURE-GENERATOR etc.
  326.  
  327. At any time, the value of the variable *make-lap-closure-generator* is a
  328. symbol which names the function currently being used to make lap closure
  329. generators.
  330.  
  331. The port specific function must accept arguments as follows:
  332.  
  333. PLAP-CLOSURE-GENERATOR (<closure-vars>
  334.                         <args>
  335.                         <index-registers>
  336.                         <simple-vector-registers>
  337.                         <lap-code>)
  338.  
  339. This returns a lap-closure generator.  A lap-closure generator is a
  340. function which is called with a number of arguments equal to the length
  341. of <closure-vars>.  These arguments are the values of the closure
  342. variables for the lap closure.  These values cannot be changed once the
  343. LAP closure is created.   PCL takes care of keeping track of
  344. lap-closure-generators it already has on hand and reusing them.  The
  345. function RESET-LAP-CLOSURE-GENERATORS can be called to force PCL to
  346. forget all the lap closure generators it has remembered.
  347.  
  348.   <args> 
  349.  
  350. A list of symbols.  This provides a way to name particular arguments to
  351. the LAP closure. Arguments which will not be referenced by name are
  352. given as NIL. All required arguments to the LAP closure are explicitly
  353. included (perhaps as NIL).  If &REST appears at the end of arguments it
  354. means that non-required arguments are allowed, these will be processed
  355. by the methods.  If &REST does not appear at the end of arguments, the
  356. lap closure should signal an error if more than the indicated number of
  357. arguments are supplied.
  358.  
  359. Examples:
  360.  
  361.  -  (obj-0 obj-1)
  362.  
  363.     Specifies a two argument lap closure.  If more or less than
  364.     two arguments are supplied an error is signalled.  Within
  365.     the actual lap code, both arguments can be referenced by
  366.     name (see the :ARG operand).
  367.  
  368.  -  (obj-0 nil &rest)
  369.  
  370.     Specifies a two or more argument lap closure.  If less than
  371.     two arguments are supplied an error is signalled.  Within
  372.     the actual lap code, the first argument can be referenced by
  373.     name (see the :ARG operand).
  374.  
  375.  
  376.   <closure-vars>
  377.  
  378. A list of symbols.  The closure will have these as closure variables.
  379. Within the lap code these can be accessed using the :CVAR operand.  The
  380. lap code cannot change these values.  SET-FUNCALLABLE-INSTANCE-FUNCTION
  381. is permitted to have the special knowledge that there are at most ?? of
  382. these and to be prepared to do something special when the funcallable
  383. instance function of a funcallable instance is set to a lap closure.
  384.  
  385.   <index-registers>
  386.  
  387. A list of register numbers.  These registers will be used only to hold
  388. indexes.  Other registers may be used to hold indexes as well, but the
  389. only values put into these registers will be indexes.
  390.  
  391.   <simple-vector-registers>
  392.  
  393. A list of register numbers.  These registers will be used only to hold
  394. simple-vectors.  Other registers may be used to hold simple-vectors as
  395. well, but the only values put into these registers will be
  396. simple-vectors.
  397.  
  398.  
  399.   <lap-code>
  400.  
  401. The actual lap code for this closure.  This is a list of LAP code
  402. opcodes.  See the section "Abstract LAP Code" for more details.
  403.  
  404. Each implementation must also supply a function named PRE-MAKE-xxx where
  405. xxx is the same as the name of its make-lap-closure-generator function.
  406. The macro doesn't evaluate its arguments, and when it appears in a file
  407. it should try to do some of the work at load time.  It might appear in a
  408. file like this:
  409.  
  410. (eval-when (load)
  411.   (setq 1-arg-std-lap 
  412.         (pre-make-plap-closure-generator ...)))
  413.  
  414. *** Abstract LAP Code ***
  415.  
  416. Each lap code operand has the form: (opcode operand1 ... operandn).
  417.  
  418. In some cases, the distinction between an operand and an opcode is
  419. somewhat arbitrary.  In general, opcodes have a significant "action"
  420. component to their behavior.  Operands select a piece of data to operate
  421. on.  Some operands select their data in a more complex way, but they are
  422. operands anyways.
  423.  
  424. All data must be in a register before it can be operated on.   This
  425. requirement means that the only place a non-register operand can appear
  426. is as the first argument to the :move opcode.  (Actually, there is one
  427. other exception, a :iref operand can be the target of a move as well.)
  428. Moreover, only register operands can appear as the second argument to
  429. the :move opcode and this register must not appear in the <from>
  430. operand.
  431.  
  432. >> The operands are:
  433.  
  434.  (:reg <n>)
  435.    
  436. A pseudo register.  <n> is an integer in the range [0 , 31].
  437.  
  438. A particular implementation can map this to a real register, a memory
  439. location or the stack.  The abstract LAP code itself does not include
  440. the notion of a stack.
  441.  
  442. PCL will attempt to optimize register use in two ways.  PCL itself will
  443. attempt to re-use registers whenever possible.  That is, the port should
  444. not have to worry with doing live register analysis for the registers.
  445. In addition, PCL will consider lower numbered registers to be "faster"
  446. than higher numbered ones.
  447.  
  448.  
  449.  (:cvar <name>)
  450.  
  451. A closure variable of the lap-closure.  <name> is a symbol.
  452.  
  453.  
  454.  (:arg <name>)
  455.  
  456. An argument to the LAP closure.  <name> is a symbol.
  457.  
  458.  (:std-wrapper <from>)
  459.  (:fsc-wrapper <from>)
  460.  (:built-in-wrapper <from>)
  461.  (:structure-wrapper <from>)
  462.  (:other-wrapper <from>)
  463.  
  464. Get the class wrapper of <from>.  For std-instances and fsc-instances
  465. this just fetches the wrapper field.  The specific port is required to
  466. implement fast access to the wrappers of built-in, structure and other
  467. metatypes.  A callback mechanism allows the port to ask PCL to generate
  468. a class and wrapper for objects for which no class and wrapper exists
  469. yet.  This mechanism is <<to be spec'd out>>.
  470.  
  471.  
  472.  (:std-slots <operand>)
  473.  (:fsc-slots <operand>)
  474.  
  475. Fetch the slots field of a std-instance or a fsc-instance.
  476.  
  477.  (:constant <constant>)
  478.  
  479. This just allows inline constants. <constant> can be any Lisp object.
  480.  
  481. The following operands operate on indexes.  Each is patterned after a
  482. Lisp function which would have a corresponding effect on the index value
  483. of the index.
  484.  
  485.  (:i1+ <index>)
  486.  (:i+ <index-1> <index-2>)
  487.  (:i- <index-1> <index-2>)
  488.  (:ilogand <index-1> <index-2>)
  489.  (:ilogxor <index-1> <index-2>)
  490.  
  491. Like the corresponding Lisp functions.  
  492.  
  493.  
  494.  (:iref <vector> <index>)
  495.  
  496. Like the SVREF function.  <vector> must be a simple vector.
  497.  
  498.  (:cref <vector> <constant>)
  499.  
  500. The :cref operand is for constant vector references.  <constant> must be
  501. a fixnum.
  502.  
  503. >> The opcodes are:
  504.  
  505.  (:move <from> <to>)
  506.  
  507. A full word move operation.  
  508.  
  509.  
  510.  (:eq <from1> <from2> <label>)
  511.  (:neq <from1> <from2> <label>)
  512.  
  513. EQ and NOT EQ conditional branches.  If the value contained in <from1>
  514. is EQ (or not) to the value contained in <from2>, jump to <label>.
  515. Otherwise continue execution at the next opcode.  <label> is a symbol.
  516.  
  517.  (:fix= <from1> <from2> <label>)
  518.  
  519. A fixnum = conditional branch.
  520.  
  521.  (:izerop <from> <label>)
  522.  
  523. Jump to label if and only if the index <from> is 0.
  524.  
  525.  (:std-instance-p <from> <destination-label>)
  526.  (:fsc-instance-p <from> <destination-label>)
  527.  (:built-in-instance-p <from> <destination-label>)
  528.  (:structure-instance-p <from> <destination-label>)
  529.  
  530. Test the metatype of <from> and dispatch.  If the metatype of from is of
  531. the specified type execution jumps to <destination-label>.  Otherwise,
  532. execution proceeds normally at the next opcode.  See the :xxx-wrapper
  533. operands.
  534.  
  535.  (:jmp <function>)
  536.  
  537. fcn contains a function to call.  This must be a compiled function,
  538. which had the PCL-FAST-CALL declaration in it.  The call should be "tail
  539. recursive" in that whatever values it returns should be returned
  540. immediately from the LAP closure itself.
  541.  
  542. Method lookup is implemented by finding a function in the cache and then
  543. using :JMP to call it.  The various kinds of traps are implemented by
  544. using :JMP to call a trap function that was stored in one of the closure
  545. variables.
  546.  
  547.  (:return <value>)
  548.  
  549. Return immediately from the LAP closure.  <value> is the single value to
  550. return.
  551.  
  552. In certain cases of method lookup when all the methods are accessor methods, 
  553. there is an important optimization which implements the slot access in the 
  554. discriminating function itself.  This opcode is used in that case.
  555.  
  556.  (:label <label>)
  557.  
  558. Identifies a point in the lap code.  <label> is a symbol.  This can be
  559. the target of any of the control transfer opcodes (:GO, :EQ, :NEQ,
  560. :FIX=, :STD-INSTANCE-P, :FSC-INSTANCE-P, :STRUCTURE-INSTANCE-P,
  561. :BUILT-IN-INSTANCE-P)
  562.  
  563.  (:go <label>)
  564.  
  565. Jump to the label <label>.  <label> is a symbol.
  566.  
  567. *** Examples ***
  568.  
  569. Here is an example of the use of the abstract LAP mechanism.  It doesn't
  570. use all operands or opcodes, but it is representative of the LAP
  571. sequences PCL will use.
  572.  
  573. Several things are worth noting:
  574.   * This is, I believe, just about the simplest such sequence.  There
  575.     are some others of comparable simplicity, but none much simpler.
  576.  
  577.   * A total of only 5 registers are used.  I haven't checked, but I
  578.     am pretty sure most all such code sequences will get by with 16
  579.     or less and many will stay under 8.
  580.  
  581. (defun make-1-arg-std-dfun ()
  582.   (let ((cg
  583.       (making-lap-closure-generator
  584.         (initialize-lap-cvars '(cache mask reflect trap))    
  585.         (initialize-lap-args '(a0))
  586.         (initialize-lap-registers 4 4 3)
  587.         (let ((cache (allocate-lap-register 'simple-vector))
  588.           (count (allocate-lap-register))
  589.           (wrapper (allocate-lap-register))
  590.           (index (allocate-lap-register 'index))
  591.           (t1 (allocate-lap-register))
  592.           (t2 (allocate-lap-register))
  593.           (i1 (allocate-lap-register 'index))
  594.           (i2 (allocate-lap-register 'index)))
  595.  
  596.           (opcode :move (operand :cvar 'cache) cache)
  597.           (opcode :move (operand :arg 'a0) t1)          
  598.           (opcode :std-instance-p t1 'std-instance)
  599.           (opcode :go 'trap)
  600.           (opcode :label 'std-instance)
  601.           ;;
  602.           ;; The stable register pattern for the rest of the code is:
  603.           ;;   cache    Cache
  604.           ;;   count    Cache count
  605.           ;;   wrapper  Wrapper
  606.           ;;   index    index under consideration
  607.           ;;
  608.           ;; Whenever we jump to HIT, this pattern must be in force.
  609.           ;;
  610.           (opcode :move (operand :std-wrapper t1) wrapper);
  611.           (opcode :move (operand :cref cache 0) count)    ;
  612.           (opcode :move (operand :cref wrapper 0) i2)     ;wrapper-no -> i2
  613.           (opcode :move (operand :cvar 'mask) i1)          ;mask       -> i1
  614.           (opcode :move (operand :ilogand i1 i2) index)   ;
  615.                                       ;
  616.           (opcode :move (operand :iref cache index) t1)   ;key        -> t1
  617.           (opcode :eq t1 wrapper 'hit)                    ;
  618.           (opcode :move (operand :cvar 'reflect) i1)      ;reflect    -> i1
  619.           (opcode :move index i2)                      ;index      -> i2
  620.           (opcode :move (operand :i- i1 i2) index)          ;
  621.           (opcode :move (operand :iref cache index) t1)   ;key        -> t1
  622.           (opcode :eq t1 wrapper 'hit)                    ;
  623.           (opcode :go 'trap)
  624.           
  625.           ;;
  626.           ;; To do the hit, we use registers as follows:
  627.           ;;   0   cache comes in here
  628.           ;;   1   cache count comes in here
  629.           ;;   2   use this for the function
  630.           ;;   3   index comes in here
  631.           ;;   4   1+ index, then new count
  632.           ;;   
  633.           (opcode :label 'hit)
  634.           (opcode :move (operand :i1+ index) i1)
  635.           (opcode :move (operand :iref cache i1) t1)
  636.  
  637.           (opcode :move (operand :cref cache 0) t2)
  638.           (opcode :fix= count t2 'call)
  639.           (opcode :go 'trap)
  640.  
  641.           (opcode :label 'call)
  642.           (opcode :jmp t1)
  643.  
  644.           (opcode :label 'trap)
  645.           (opcode :move (operand :cvar 'trap) t1)
  646.           (opcode :jmp t1)))))
  647.  
  648.     (funcall cg
  649.          (make-array 16)
  650.          (make-index-mask 16 2)
  651.          (- 16 2)
  652.          #'(lambda (a)
  653.          (declare (pcl-fast-call) (ignore a))
  654.          (break "Trap")))))
  655.  
  656.